Skip to content

fix(a11y): pair Toast variant background with a severity icon (WCAG 1.4.1)#3449

Merged
bilal-karim merged 2 commits into
mainfrom
a11y/1.4.1-toast-severity-icon
Jun 4, 2026
Merged

fix(a11y): pair Toast variant background with a severity icon (WCAG 1.4.1)#3449
bilal-karim merged 2 commits into
mainfrom
a11y/1.4.1-toast-severity-icon

Conversation

@bilal-karim
Copy link
Copy Markdown
Member

@bilal-karim bilal-karim commented May 25, 2026

Summary

Toast variants (success/error/info/warning) previously distinguished severity exclusively by background color. Sighted users with color-vision differences (deuteranopia, protanopia, achromatopsia) could not distinguish a success toast from an error toast on the surface itself — only bg-success versus bg-danger.

This PR adds a leading Icon keyed to the variant via a variantIcon lookup. The neutral primary variant (no severity intent) renders no icon. Icon names mirror the established alert.svelte pattern.

+  const variantIcon: Readonly<Record<ToastVariant, IconName | null>> = {
+    primary: null,
+    success: 'success',
+    error: 'error',
+    info: 'info',
+    warning: 'warning',
+  };
...
+  {#if variantIcon[variant]}
+    <Icon name={variantIcon[variant]} class="shrink-0" />
+  {/if}
   <p class="text-sm">
     <slot />
   </p>

Before / After

Before — color-only severity After — color + icon severity
image image

success, error, info, warning each gain a leading severity icon. The neutral primary variant renders no icon (no severity intent). Background colors and message text are unchanged.

The accessible announcement is unchanged — Toaster's role="log" still carries the message text via the slot. The new icon is purely decorative.

Audit context

  • WCAG 2.2 SC 1.4.1 Use of Color (Level A) — High remediation priority.
  • Toasts are the product's primary confirmation channel for write operations (cancel, terminate, reset, signal, pause, batch action, deployment edit). Misreading a failure as a success here degrades operational trust.
  • Issue file: audit-output/issues/1.4.1-toast-severity-icon.md (part of the Wave 2 Holocene severity-primitive bundle — Toast is the first of four; Badge / Chip / Banner ship as separate PRs).

Affected callsites (all benefit automatically)

Every toaster.push({ variant, message }) consumer inherits the fix. No per-consumer change required. Includes activity-options-update, activity reset/cancel/terminate confirmations, all workflow client-actions (batch cancel/terminate/reset, pause/unpause, signal, update), event-history import, deployment edits, and file upload.

Test plan

  • Storybook: visual diff in toaster.stories.svelte — all 5 variants render correctly; primary shows no icon.
  • Screen reader smoke test (VoiceOver/NVDA): the message text continues to announce via role="log"; the decorative icon does not double-announce.
  • Trigger a cancel-confirmation flow (success toast) and a terminate-failure flow (error toast); confirm the icons render and are visually distinguishable.
  • Color-blind simulator (deuteranopia, protanopia, achromatopsia): the variant icon shape is perceptible against each variant background.
  • axe-core: no new violations.

Out of scope

  • Upgrading role="log"role="alert" for error toasts (that's SC 4.1.3 Status Messages).
  • Migrating to a title-plus-body structured layout.
  • Toast deduplication, queue limits, animation tuning.

🤖 Generated with Claude Code

A11y-Audit-Ref: 1.4.1-holocene-severity-primitives-wave2

….4.1)

Toast variants (success/error/info/warning) previously
distinguished severity exclusively by background color. Sighted
users with color-vision differences (deuteranopia, protanopia,
achromatopsia) could not tell a success toast from an error
toast on the surface itself.

Add a leading Icon keyed to the variant: success/error/info/
warning each render their named icon; the neutral 'primary'
variant (no severity) renders no icon.

Icon names match the existing alert.svelte pattern and resolve
to confirmed entries in the IconName set. The accessible
announcement is unchanged -- Toaster's role="log" still carries
the message text via the slot, and the new icon is purely
decorative.

Affects every toaster.push consumer including cancel/terminate/
reset/signal/pause confirmations, batch actions, deployment
edits, and file upload flows. Single primitive-level change;
no consumer migration required.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@bilal-karim bilal-karim requested a review from a team as a code owner May 25, 2026 16:30
@vercel
Copy link
Copy Markdown

vercel Bot commented May 25, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
holocene Ready Ready Preview, Comment May 25, 2026 4:42pm

Request Review

temporal-cicd flagged a TS warning on the conditional Icon render:
the expression variantIcon[variant] is typed IconName|null but
Icon's name prop expects IconName. Svelte template doesn't narrow
the type through the {#if} guard.

Extract the lookup into a reactive `$: icon = variantIcon[variant]`
in the script so the {#if icon} guard narrows icon to IconName
(non-null) for the Icon prop. Pure type refactor -- runtime
behavior identical.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions github-actions Bot added a11y Accessibility audit PR a11y:no-fix-doc No A11y-Audit-Ref line; audit team triage a11y:bucket-3 Bucket 3: engineer required a11y:sc-1.4.1 and removed a11y:no-fix-doc No A11y-Audit-Ref line; audit team triage labels May 28, 2026
@ardiewen
Copy link
Copy Markdown
Contributor

ardiewen commented Jun 4, 2026

Follow-up: consolidate the severity → icon map (deferred)

This PR intentionally keeps the variantIcon map local to Toast. Since this is PR 1 of 4 in the Wave-2 severity-primitive bundle (A11y-Audit-Ref: 1.4.1-holocene-severity-primitives-wave2), the same { success, error, info, warning } → icon relationship will recur in Badge / Chip / Banner, and alert.svelte already encodes it implicitly (icon = intent).

Rather than guess the shared shape now, the Badge/Chip/Banner PRs should consolidate these into one shared severity → icon map/helper (preserving Toast's primary → null no-severity case) and reconcile alert.svelte onto it — avoiding ~5 near-duplicate lookups. Flagging here so it isn't lost across the four PRs.

bilal-karim added a commit that referenced this pull request Jun 4, 2026
…#3450)

The Chip primitive's 'warning' intent previously applied bg-danger
as the only severity signal. Sighted users with color-vision
differences (deuteranopia, protanopia, achromatopsia) could not
identify which chip in a list was the rejected one -- all chips
looked similar in shape.

Render a leading 'warning' Icon when intent === 'warning'. The
icon is decorative (aria-hidden via svg.svelte's default when no
title is passed); the existing aria-live="assertive" hint on the
parent ChipInput continues to carry the AT-readable signal for
validation failures.

Affects ChipInput (chip-input.svelte:128, 189) and Combobox
(combobox.svelte:518) where intent={valid ? 'default' : 'warning'}
is set per chip. Search-attribute filtering, email/ID list inputs,
and similar form surfaces inherit the fix from the primitive.

Mirrors the pattern established by PR #3449 (Toast severity icon)
and the existing Alert primitive (icon defaults to intent).

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
@bilal-karim bilal-karim merged commit b64ed71 into main Jun 4, 2026
18 checks passed
@bilal-karim bilal-karim deleted the a11y/1.4.1-toast-severity-icon branch June 4, 2026 15:42
rossedfort added a commit that referenced this pull request Jun 4, 2026
Auto-generated version bump from 2.50.0 to 2.51.0

Bump type: minor

Changes included:
- [`495e27f7`](495e27f) Use pageSize instead of maximumPageSize (#3422)
- [`42c58c45`](42c58c4) fix: use rem-based width for expanded side-nav (#3426)
- [`ce928ed5`](ce928ed) fix(a11y): meet 4.5:1 contrast for text-subtle and text-warning (WCAG 1.4.3) (#3437)
- [`e8ed8a49`](e8ed8a4) fix(a11y): consistent high-contrast focus rings across all Button variants (WCAG 1.4.11) (#3438)
- [`03918fb5`](03918fb) fix(a11y): prevent horizontal scroll on login page at 320px (WCAG 1.4.10) (#3440)
- [`a11555fb`](a11555f) fix(a11y): use rem-based font-size and unitless line-height in markdown reset (#3430)
- [`057ff323`](057ff32) fix: use empty alt on decorative SDK logo image (#3424)
- [`e435ffd7`](e435ffd) Standalone Activity details page UI updates (#3427)
- [`e09028f3`](e09028f) Make bottom-nav accept linksSnippet instead of sections (#3445)
- [`a22a1e21`](a22a1e2) Add tooltip for SDK version (#3462)
- [`c3b7820e`](c3b7820) [DT-4039] Workflow query builder doesn't work with numeric search attributes (#3435)
- [`5e53881f`](5e53881) Make Common Errors dismissable (#3471)
- [`2665e2f1`](2665e2f) [DT-4048] Add accessibility PR triage and notification helpers (#3465)
- [`8083cf4e`](8083cf4) feat(DT-4017): Schedules List UI Update (#3467)
- [`b5de30e3`](b5de30e) fix(markdown): allow nested lists to render as block (DT-4047) (#3463)
- [`8a6fd2de`](8a6fd2d) Add a prop to hide select/deselect controls (#3474)
- [`33ec1b3e`](33ec1b3) DT-4051: pre-populate input when starting standalone activity like this one (#3469)
- [`0d64fc20`](0d64fc2) fix(a11y): reveal Copyable's CopyButton on focus-within (WCAG 2.1.1) (#3452)
- [`321651c0`](321651c) fix: accept autocomplete prop on NumberInput, ChipInput, and Combobox (#3425)
- [`6567c222`](6567c22) fix: cap ZoomSvg container height to viewport (#3428)
- [`afd3a8ed`](afd3a8e) fix(a11y): accommodate text-spacing overrides on Badge and Chip (WCAG 1.4.12) (#3433)
- [`d264b876`](d264b87) fix(a11y): non-color signal for Label required indicator (WCAG 1.4.1) (#3439)
- [`73c61ea3`](73c61ea) fix(a11y): tabindex on <main> so the skip link moves focus reliably (WCAG 2.4.1) (#3451)
- [`34e582a8`](34e582a) fix(a11y): info-and-relationships compliance (WCAG 1.3.1) (#3432)
- [`f7be7b6c`](f7be7b6) fix(query): quote ExecutionDuration Go duration strings in visibility SQL (#3482)
- [`9f0c2631`](9f0c263) Passthrough goto params to link component (#3483)
- [`2f8c037b`](2f8c037) feat: add centerButton, menuButton, and linksContent snippets to BottomNavigation (#3485)
- [`7f258bac`](7f258ba) fix: add "for" to validate connection modal title (#3488)
- [`350397ab`](350397a) feat(DT-4069): Update modal backdrop to 50% opaque (#3486)
- [`f01baa49`](f01baa4) refactor: Input & DatePicker - Svelte 5 & afterLabel snippet (#3479)
- [`587c892f`](587c892) Fix decoded object payload summaries (#3491)
- [`2b85ef06`](2b85ef0) fix(DT-4044): only use browser codec endpoint when override option is selected (#3490)
- [`468893ba`](468893b) fix(a11y): improve 1.1.1 non-text content compliance (#3431)
- [`3e8c996d`](3e8c996) fix(a11y): make Tooltip keyboard-accessible, dismissible, and hoverable (#3429)
- [`47552538`](4755253) fix(a11y): name-role-value compliance, partial (WCAG 4.1.2) (#3434)
- [`e2c95d54`](e2c95d5) fix(a11y): align DOM order with visual order in mobile bottom-nav (WCAG 1.3.2) (#3441)
- [`634b08e1`](634b08e) a11y(1.4.11): focus rings — lighten dark-mode --color-border-focus-info to indigo.400 so ring-primary/70 meets 3:1 against surface-primary (#3478)
- [`039a555a`](039a555) a11y(1.4.11): Checkbox — add ring-offset so the checked-state focus ring contrasts against the indigo checked background (#3477)
- [`01161e2e`](01161e2) a11y(1.4.10): activity-options drawer — make width responsive so it reflows at 320 px (#3476)
- [`99f0a0a4`](99f0a0a) fix(a11y): filter hidden nav items in mobile bottom-nav (WCAG 4.1.2) (#3494)
- [`d12ebacc`](d12ebac) fix(a11y): add focusin/focusout to saved-query nav hover tooltip (WCAG 2.1.1) (#3453)
- [`c7206af9`](c7206af) fix(a11y): render a warning icon for Chip warning intent (WCAG 1.4.1) (#3450)
- [`b64ed713`](b64ed71) fix(a11y): pair Toast variant background with a severity icon (WCAG 1.4.1) (#3449)
- [`8e2ef708`](8e2ef70) upgrade temporal api version to latest (v1.62.13) (#3502)

Co-authored-by: rossedfort <11775628+rossedfort@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a11y:bucket-3 Bucket 3: engineer required a11y:sc-1.4.1 a11y Accessibility audit PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants